package dongshi.daddy.byzantine.signature;

import cn.hutool.json.JSONUtil;
import dongshi.daddy.encryptionAndDecryption.rsa.Md5Utils;
import dongshi.daddy.encryptionAndDecryption.rsa.RsaUtil;
import org.apache.commons.codec.binary.Base64;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

// 3个将军场景，将军A，将军B，将军C，其中将军C是叛徒，将军A先发消息
public class Example1 {
    private static String IP = "127.0.0.1";
    private static int A_PORT = 6666;
    private static int B_PORT = 6667;
    private static int C_PORT = 6668;
    // 进攻消息
    private static String MESSAGE_ATTACK = "attack";
    // 撤退消息
    private static String MESSAGE_RETREAT = "retreat";
    // 等待所有将军准备好接收消息
    private static CountDownLatch waitAllGeneralReady = new CountDownLatch(3);
    private static CountDownLatch mainDoNotExist = new CountDownLatch(1);
    private static CountDownLatch firstRoundMsgAllReceived = new CountDownLatch(2);
    private static CountDownLatch roundTwoReadyForReceiveMsg = new CountDownLatch(2);
    private static CountDownLatch roundTwoReceiveOk = new CountDownLatch(2);

    public static void main(String[] args) {
        Thread generalA = new Thread(() -> {
            try {
                System.out.println("将军A准备好了！");
                String GENERATE_A_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIv2C0FF5y/8I6UH5f1L88lqDYI4cYKDyArmgRo/x2GYAXgMkmzXRy8L54+oQYI3WVCHZLH8UdGeZhA/CesW97WHGhtcRwFamdiZaXLUNbmYeTcTJeVKHHvnaCot61M/Zg4mNJYkg0GYtOqN3TIz5zPjCSLEkhqq4WRntI6zIpahAgMBAAECgYAOKCwJRS3WT7W3Sq47ycP1+pE1O8pXfkbsu1YRtqPPPlXfs73X3oqnupeIk2dU6Y5SM+++hRwtjM0cFZRGQEN75I79oG5UtAeL0+yEAjxwgOEzWkpzpHxNXV2T74HpbTxBMei0NeJxrdKcV/zB8UeCwUAFk5Gyk82cBHkhJKru8QJBAPP6qwMsoYZJHCjS6PYUhOnidxQvvzV0pH9NPFXCJKx9WCEfiyBFAzfSkRLgRlg5L7eIf201A+PPb4tQdspi2D0CQQCS22LuZxCcCf46H4UWsCRLqRWrtU+2qTzK0MVA3FWSkuKAbr+cc2Yu+nDcJb5TatG+aitBJcg1eOtlcPfTRjo1AkEAoflX0Wqu7YzaAgxLlxt2RiQQNDmisqV2lAE9vh1+5XjtqkfbwJewlD3aDHMSoH2OvUUEfAcHRxWvEwlfY+uNbQJADtkDfZsKANy5yWKacBrK8KNdv4iLUKp3rQHCj7Y3KKSVJesMkGLSd/l2I6vlPEXWguv1B5csUMuy8QSk6cV16QJBAN6ekB7RA9JeeUvvFmQt5VwOCFkMuvHdnq1x/JnL+BB/BMqWZQlfIk28our4ORSytz6aZCtX62sTZxQS3vV+HLQ=";
                byte[] GENERATE_A_PRIVATE_KEY_BYTE = Base64.decodeBase64(GENERATE_A_PRIVATE_KEY.getBytes());
                waitAllGeneralReady.countDown();
                // 第一轮将军A发送攻击消息给将军B和将军C
                Order attackOrder = new Order();
                attackOrder.setMsg(MESSAGE_ATTACK);
                attackOrder.setDigest(Md5Utils.md5(MESSAGE_ATTACK));
                byte[] encryptResultByte = RsaUtil.encryptByPrivateKey(Md5Utils.md5(MESSAGE_ATTACK).getBytes(), GENERATE_A_PRIVATE_KEY_BYTE);
                attackOrder.setSignature(Base64.encodeBase64String(encryptResultByte));
                attackOrder.setWhoAmI(General.A.getName());
                // 所有将军准备完毕，开始发送第一轮的攻击消息
                waitAllGeneralReady.await();
                System.out.println("将军A开始发送第一轮攻击消息");
                sendMsg(JSONUtil.toJsonStr(attackOrder), B_PORT);
                sendMsg(JSONUtil.toJsonStr(attackOrder), C_PORT);
//                sendMsg(sendToA, A_PORT);
                roundTwoReceiveOk.await();
                System.out.println("将军A最终执行的指令是：" + MESSAGE_ATTACK);

            } catch (Exception e) {
            }
        });
        generalA.start();

        Thread generalB = new Thread(() -> {
            try {
                final String GENERATE_B_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALNwJEbd6IQVYluIShznGpk8UUPfpnimly+ewuDgaoFCm9rsSC51pq6jdZTLpxySt9/1KMZFUQTop/W0EcCUbs6dTYfWnHAN0t5SF7pjRjUSkl/Ga+cKa0dmNB6oZr8Z3pIAVXfwWmTFj+aaz+c5jxGs8JMmD6BvF3F3+nkszcz/AgMBAAECgYAEaS5LG6tai9gGHfyMZ1XtDGGHBJln/9hZkFKECqAzmzqPkB1aHwRA6L28MaFi/2xLshAVh6CtOTbfwFRrYnqyyPj5XjXf2xCVQkSWQWaqkQBC5bH5f1eeww7No2jxZ/0YH53rH7OSvRUGKgeUEL2PuHZLNA4Pjc4T+/INKqhjcQJBAOACwbNCbq3hk+kL6PWRMtMsdSj/RCwGjCk+6kBzNqNi5/ZeBClgirmcL8hLdByYfmNTHaAXHF1gEGwdHISm7bcCQQDND+yQe4Y7Kkl7Bve6J21neF2rvxQeksxK2hRuiHVnoAlFO4GA8id9SnBCRTB679P1jPxhYUxQYmWmooxgFBr5AkEAz7aep7rE6CTQQvsRM4tEhn2l0bMJIo2br68wBT0P/+Z5UoAQ+dEUMVG7GM2mR9G6+BycztI/MfIeqDFCPVqP9wJBAKovCw+l1HMJ103pLHMsAZobxPGidRoijrWYY9iQG4/Su57fkczoK2nJ+3ykW2XL4EcrqNFQpLsZzm8K/CFIzlECQAN9PQEYpi9IAm7Q1Z8eOS/ERuesAgoUKWf62ieAFhDde7Dgq75GnRFqjeVIi//GpAXZEBVMgrGM9MLoT5IUQKA=";

                ServerSocket serverB = new ServerSocket(B_PORT);
//                System.out.println("B准备接收消息");
                System.out.println("将军B准备好了！");
                waitAllGeneralReady.countDown();
                String firstReceive = receiveMsg(serverB);
                System.out.println("将军B第一轮收到的消息是：" + firstReceive);
                List<Order> receivedOrderList = new ArrayList<>();
                // TODO 验证消息
                Order firstRoundReceivedOrder = JSONUtil.toBean(firstReceive, Order.class, true);
                receivedOrderList.add(firstRoundReceivedOrder);
                firstRoundMsgAllReceived.countDown();
                firstRoundMsgAllReceived.await();

                // 准备接收C的第二轮消息（用异步）
                new Thread(() -> {
                    String roundTwoReceivedMsg = null;
                    try {
                        roundTwoReceivedMsg = receiveMsg(serverB, roundTwoReadyForReceiveMsg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.out.println("将军B第2轮收到的消息是：" + roundTwoReceivedMsg);
                    // 验证消息是否被篡改
                    boolean msgOk = validateMsg(roundTwoReceivedMsg, KeyHolder.GENERATE_B_PUBLIC_KEY, KeyHolder.GENERATE_A_PUBLIC_KEY);
                    if (!msgOk) {
                        System.out.println("将军B第二轮收到了篡改的消息，忽略！");
                    }
                    roundTwoReceiveOk.countDown();
                }).start();
                // 准备发送第二轮消息(保证countDown后accept执行完毕)
                Thread.sleep(500);
                Order attackOrder = new Order();
                attackOrder.setMsg(firstRoundReceivedOrder.getMsg());
                attackOrder.setDigest(Md5Utils.md5(firstRoundReceivedOrder.getMsg()));
                byte[] GENERATE_B_PRIVATE_KEY_BYTE = Base64.decodeBase64(GENERATE_B_PRIVATE_KEY.getBytes());
                byte[] encryptResultByte = RsaUtil.encryptByPrivateKey(firstRoundReceivedOrder.getMsg().getBytes(), GENERATE_B_PRIVATE_KEY_BYTE);
                attackOrder.setSignature(Base64.encodeBase64String(encryptResultByte));
                attackOrder.setWhoAmI(General.B.getName());
                // 转发第一轮的指令
                attackOrder.setOriginOrder(firstRoundReceivedOrder);
                sendMsg(JSONUtil.toJsonStr(attackOrder), C_PORT);
/*
                String roundTwoReceivedMsg = receiveMsg(serverB);
                System.out.println("将军B第2轮收到的消息是：" + roundTwoReceivedMsg);
                // 验证消息是否被篡改
                boolean msgOk = validateMsg(roundTwoReceivedMsg, KeyHolder.GENERATE_C_PUBLIC_KEY, KeyHolder.GENERATE_A_PUBLIC_KEY);
                if (msgOk) {
                    System.out.println("将军C篡改了消息，忽略！");
                }
*/
//                roundTwoReceiveOk.countDown();
                roundTwoReceiveOk.await();
                // 最终执行的指令 排序，取n/2位置结果
                Set<String> resultOrderSet = new HashSet<>();
                receivedOrderList.forEach(v -> resultOrderSet.add(v.getMsg()));
                System.out.println("将军B最终执行的指令是：" + resultOrderSet.toArray(new String[] {})[resultOrderSet.size() / 2]);

            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        generalB.start();

        Thread generalC = new Thread(() -> {
            try {
                final String GENERATE_C_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOSIKdBMr0IJQIorMukxVTi3feuGj5ZcZtkd71QIzBXcV+2lScv1lQEt8iuSGki2ii1M840NZ+uafLoypvH2npeVFZc8Sau67ko/WCY9gVWMhBYHrZP5omMg/cV98F66hGRY8LuRRIdIuPLGZaY+HRI+KaPaufQED7IUo/Ion2pvAgMBAAECgYBZG8bCE8SaiSUDOwjk6HmEXffZaGSAipMHwaZaK/v34ize2v5FawKorROqbZOWGqcSNtkYfMuykFBmAe4alsELOVPsRgLUMJiTU9XUQdg9xufml9CPE7SbqyXGDIPUpRnah2S8PV2hjjrLlo1GEmyqXkffCo29zrcQszOCTmG88QJBAPJ9ZuHwrKINYPQgCMVJBD+p/5ytE58wCumVAMWaM1WC3r+6PCN5XZcItvbCmzzyMJLlOTkPqsiDfvthLlza5C0CQQDxQ65XB9lBQ3L6JFayGViAbYR6hLCjLjVbMvrVnEOMc6uxyM3orPq7KXQJ5Jgzh9czY7MT0o4IG0M0d7RzEV6LAkEA0Xh8eG70YeSWvSwzB2GE1w4eLhTg32Gb+51olkoXUCYGD69sJeTAcSjpDI6dlW9NgIec/+rFyAK3naWcbD/2XQJATsq2tg+HISxA8XZKpfFnGNIrcMPC2BfwLu+QQZC98rGYAgC9Vl6fp94a2lxAZJvZaRwHUNLztawJn6n0UZv7jwJAeLYIKpiE1TpsND6owSsTqCKw0WVVZwCs+RjcjJ1lLA7Map64SB4YLklBFiCP/OIEPEbjjFZAzSpjn8UERWNAXg==";

                ServerSocket serverC = new ServerSocket(C_PORT);
                System.out.println("将军C准备好了！");
                waitAllGeneralReady.countDown();
                //                System.out.println("B准备接收消息");
                String firstReceive = receiveMsg(serverC);
                System.out.println("将军C第一轮收到的消息是：" + firstReceive);
                List<Order> receivedOrderList = new ArrayList<>();
                // TODO 验证消息
                Order firstRoundReceivedOrder = JSONUtil.toBean(firstReceive, Order.class, true);
                receivedOrderList.add(firstRoundReceivedOrder);
                firstRoundMsgAllReceived.countDown();
                firstRoundMsgAllReceived.await();
                // 准备接收C的第二轮消息（用异步）
                new Thread(() -> {
                    String roundTwoReceivedMsg = null;
                    try {
                        roundTwoReceivedMsg = receiveMsg(serverC, roundTwoReadyForReceiveMsg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    System.out.println("将军C第2轮收到的消息是：" + roundTwoReceivedMsg);
                    // 验证消息是否被篡改
                    boolean msgOk = validateMsg(roundTwoReceivedMsg, KeyHolder.GENERATE_C_PUBLIC_KEY, KeyHolder.GENERATE_A_PUBLIC_KEY);
                    if (!msgOk) {
                        System.out.println("将军C第二轮收到了篡改的消息，忽略！");
                    } else {
                        System.out.println("将军C第二轮收到消息正常！");
                        receivedOrderList.add(JSONUtil.toBean(roundTwoReceivedMsg, Order.class, true));
                    }
                    roundTwoReceiveOk.countDown();
                }).start();
                // 准备发送第二轮消息(保证countDown后accept执行完毕)
                Thread.sleep(500);
                Order attackOrder = new Order();
                // 篡改指令为撤退 falsify 英 [ˈfɔːlsɪfaɪ]
                String falsifiedOrder = MESSAGE_RETREAT;
//                attackOrder.setMsg(firstRoundReceivedOrder.getMsg());
                attackOrder.setMsg(falsifiedOrder);
                attackOrder.setDigest(Md5Utils.md5(firstRoundReceivedOrder.getMsg()));
                byte[] GENERATE_B_PRIVATE_KEY_BYTE = Base64.decodeBase64(GENERATE_C_PRIVATE_KEY.getBytes());
                byte[] encryptResultByte = RsaUtil.encryptByPrivateKey(firstRoundReceivedOrder.getMsg().getBytes(), GENERATE_B_PRIVATE_KEY_BYTE);
                attackOrder.setSignature(Base64.encodeBase64String(encryptResultByte));
                attackOrder.setWhoAmI(General.C.getName());
                // 转发第一轮的指令,将军C是叛徒，篡改指令为撤退
                firstRoundReceivedOrder.setMsg(falsifiedOrder);
                attackOrder.setOriginOrder(firstRoundReceivedOrder);
                sendMsg(JSONUtil.toJsonStr(attackOrder), B_PORT);
//                roundTwoReceiveOk.countDown();
/*
                // 最终执行的指令 排序，取n/2位置结果
                Set<String> resultOrderSet = new HashSet<>();
                receivedOrderList.forEach(v -> resultOrderSet.add(v.getMsg()));
                System.out.println("将军C最终执行的指令是：" + resultOrderSet.toArray(new String[] {})[resultOrderSet.size() / 2]);
*/
            } catch (Exception e) {
            }
        });
        generalC.start();

        try {
            mainDoNotExist.await();
        } catch (Exception e) {}
    }

    /**
     *
     * @param roundTwoReceivedMsg
     * @param transferMsgKey 当前转发发送者的公钥key
     * @param originMsgKey 原始发送者的公钥key
     * @return
     */
//    private static String validateMsg(String roundTwoReceivedMsg, String transferMsgKey, String originMsgKey) {
    private static boolean validateMsg(String roundTwoReceivedMsg, String transferMsgKey, String originMsgKey) {
        try {
            // 原始消息
            Order order = JSONUtil.toBean(roundTwoReceivedMsg, Order.class, true);
            // TODO 验证转发消息是否被篡改
            // 转发的消息
            Order originOrder = order.getOriginOrder();
            // 验证原始消息是否被篡改
            String originOrderMsg = originOrder.getMsg();
            String originOrderDigest = originOrder.getDigest();
            // Base64.encodeBase64String
            String originOrderSignature = originOrder.getSignature();
            // 通过签名获取摘要
            String decryptOriginDigest = RsaUtil.decryptByPublicKey(Base64.decodeBase64(originOrderSignature), Base64.decodeBase64(originMsgKey.getBytes()));
            // md5原始消息的摘要
            String originMsgDigest = Md5Utils.md5(originOrderMsg);
//            if (!decryptOriginDigest.equalsIgnoreCase(originMsgDigest)) {
            if (!decryptOriginDigest.equalsIgnoreCase(originMsgDigest)) {
//                System.out.println("消息被篡改！");
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
//        return null;
        return true;
    }
    private static String receiveMsg(ServerSocket serverA, CountDownLatch countDownLatch) throws IOException {
        countDownLatch.countDown();
        Socket socketA = serverA.accept();
        InputStream is = socketA.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        while (true) {
            try {
                String s = br.readLine();
                if (s == null) {
                    break;
                    // 模拟第二轮发送消息，这里发送的是第一轮收到的消息
                }
                return s;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    private static String receiveMsg(ServerSocket serverA/*, String show*/) throws IOException {
        Socket socketA = serverA.accept();
        InputStream is = socketA.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        while (true) {
            try {
                String s = br.readLine();
                if (s == null) {
                    break;
                    // 模拟第二轮发送消息，这里发送的是第一轮收到的消息
                }
                return s;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    private static void sendMsg(String sendMsg, int port) throws IOException {
        Socket socket = new Socket("127.0.0.1", port);
        OutputStream os = socket.getOutputStream();
        PrintWriter pw = new PrintWriter(os);
        pw.println(sendMsg);
        pw.flush();
        socket.close();
    }

}
